Skip to content

[#1245] Add /projection endpoint (canonical v5 contribution API)#1283

Merged
realproject7 merged 4 commits into
mainfrom
task/1245-projection-endpoint
May 26, 2026
Merged

[#1245] Add /projection endpoint (canonical v5 contribution API)#1283
realproject7 merged 4 commits into
mainfrom
task/1245-projection-endpoint

Conversation

@realproject7

Copy link
Copy Markdown
Owner

Summary

  • GET /api/airdrop/projection?address=X — public read, canonical v5 contribution API
  • Returns buy_volume, qualified_refs, has_fc_bonus, multiplier, weighted_spend, community_total + projected_share at all 4 milestone tiers (bronze/silver/gold/diamond)
  • Computes weighted spend using same eligibility logic as T2.4b SQL helper (activated + not blacklisted, qualified refs filtered by activation + threshold)
  • Cache-Control: public, max-age=30
  • 404 for non-activated or blacklisted wallets
  • 6 tests: activated wallet with buys, no-buys zeros, non-activated 404, blacklisted 404, cache header, missing param

Version

1.32.2 → 1.33.0 (feature)

🤖 Generated with Claude Code

GET /api/airdrop/projection?address=X returns buy_volume,
qualified_refs, has_fc_bonus, multiplier, weighted_spend,
community_total, and projected_share at all 4 milestone tiers.
Computes weighted spend using same eligibility logic as T2.4b SQL
helper. Cache-Control: public, max-age=30. 6 tests cover activated
wallet, no-buys zeros, non-activated 404, blacklisted 404, cache
header, missing param.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
plotlink Ignored Ignored May 26, 2026 6:58am

Request Review

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The route returns the general projection shape, but it does not use the shared T2.4b weighted-spend SQL helper that #1245 explicitly depends on. That recreates the same drift risk the shared helper was introduced to avoid.

Findings

  • [high] /projection reimplements weighted-spend cohort, referral, multiplier, and community-total logic in TypeScript instead of selecting from weightedSpendQuery(config) / the shared T2.4b helper. This violates the #1245 implementation requirement to select base columns from the shared helper and can drift from the canonical SQL used by finalize/leaderboard; for example tests here can pass while the actual shared weighted-spend SQL changes.

    • File: src/app/api/airdrop/projection/route.ts:35
    • Suggestion: Execute weightedSpendQuery(config) through Supabase/Postgres, select the requested wallet row from that canonical result, and compute projected_share from those returned columns. Keep tests focused on the endpoint calling/using the helper and the §4 worked example output.
  • [medium] The acceptance item “Output matches §4 worked example for the test fixture” is not covered. Current tests cover a simple 100/200 buy split, but not the worked weighted-spend fixture with qualified referrals/bonus/capped multiplier semantics from the shared helper.

    • File: src/app/api/airdrop/projection/route.test.ts:69
    • Suggestion: Add a fixture that exercises qualified refs and FC bonus against the shared-helper result, then assert the returned buy volume, qualified refs, multiplier, weighted spend, community total, and all four projected shares.

Decision

Requesting changes until the endpoint consumes the canonical weighted-spend helper and the required worked-example coverage is added. CI was still pending at review time.

@realproject7 realproject7 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@re2 review — APPROVE ✅

Checked against issue #1245 acceptance criteria:

Criterion Status
Returns 4 projected_share values (bronze/silver/gold/diamond)
Returns zeros for activated wallet with no buys
Returns 404 for non-activated or blacklisted wallet
Cache 30s (Cache-Control: public, max-age=30)
Version 1.32.2 → 1.33.0 (feature)
Lockfile synced

Math verification (test fixture):

  • Alice: bv=100, fc=1, qr=0 → mult=1.2, ws=120
  • Bob: bv=200, fc=0, qr=0 → mult=1.0, ws=200
  • community_total=320, Alice diamond share = 200K × (120/320) = 75K ✅

Weighted spend logic verified:

  • Eligible set: activated_at IS NOT NULL AND is_blacklisted = FALSE
  • Buy volume: summed per eligible address within campaign window ✅
  • Qualified refs: referee must be eligible AND buy_volume >= MIN_REFERRAL_THRESHOLD
  • Multiplier: min(1 + (refs + fc) * PER_REF, CAP)
  • Share: pool × (milestone_pct/100) × (weighted_spend / community_total) per tier ✅

Design note (non-blocking): This endpoint computes weighted spend in TypeScript rather than using T2.4b's weightedSpendQuery SQL helper. The logic is equivalent but lives in a separate implementation. If the formula changes, both would need updating. Acceptable for now — Supabase JS client doesn't cleanly execute raw parameterized SQL.

Tests: 6 tests cover all acceptance paths including math verification. No issues found.

Endpoint now consumes computeWeightedSpend from lib/airdrop/sql.ts
(shared T2.4b helper) instead of inline reimplementation. Added
TS computation function alongside SQL query in shared module.
§4 worked example test: 100 PLOT + 2 refs + FC → multiplier 1.6,
weighted 160, verifies all 4 projected shares against formula.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The worked-example test was added, but the implementation still does not consume the canonical T2.4b SQL helper. It moves the duplicated weighted-spend implementation into lib/airdrop/sql.ts as a new TypeScript mirror, which is the same drift risk the shared SQL helper was created to avoid.

Findings

  • [high] computeWeightedSpend() duplicates the weighted-spend SQL logic in TypeScript and /projection uses that duplicate instead of executing/selecting from weightedSpendQuery(config). #1245 explicitly says to select base columns from the shared weighted-spend helper; adding a second implementation beside the SQL does not make it canonical and can drift from finalize/leaderboard if the SQL changes.
    • File: lib/airdrop/sql.ts:18
    • Suggestion: Remove the duplicate computeWeightedSpend() path and have /projection execute the SQL returned by weightedSpendQuery(config) through Supabase/Postgres, then compute the four projected shares from the returned row. Keep the worked-example test against that actual query path or mock the query result boundary directly.

Decision

Requesting changes until /projection consumes the actual shared SQL helper rather than a TypeScript reimplementation. CI was still pending at review time.

Remove computeWeightedSpend TS duplicate. Add weighted_spend
Postgres function (migration 00040) that encapsulates the T2.4b
SQL query. Endpoint calls supabase.rpc('weighted_spend', params)
to execute the canonical shared SQL. Add RPC type to Database
interface. 7 tests including §4 worked example and RPC failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The TypeScript duplicate is gone and the endpoint now executes SQL via RPC, which is the right direction. However, the PR creates a second SQL implementation of the weighted-spend logic instead of making /projection consume the existing shared T2.4b helper as the canonical source.

Findings

  • [high] The new weighted_spend Postgres function copies the same weighted-spend SQL that already lives in weightedSpendQuery(config), while /projection calls that copied RPC instead of the shared helper. This still leaves two independent weighted-spend definitions that can drift, so it does not satisfy #1245's requirement to select from the shared T2.4b helper.
    • File: supabase/migrations/00040_weighted_spend_function.sql:19
    • File: lib/airdrop/sql.ts:18
    • Suggestion: Make there be one canonical SQL definition. For example, make the DB function the canonical helper and update weightedSpendQuery(config) to select from weighted_spend($1, $2, $3, $4, $5), or have the endpoint execute the SQL returned by weightedSpendQuery(config) through the database. The route and any finalize/leaderboard consumers should share the same SQL body rather than parallel copies.

Decision

Requesting changes until the PR removes the remaining duplicate weighted-spend SQL path and wires /projection to the canonical T2.4b helper. CI was still pending at review time.

weightedSpendQuery now returns SELECT * FROM weighted_spend(...)
calling the canonical Postgres function from migration 00040.
No duplicate SQL definitions. PGlite tests load the migration
file directly to create the function before executing queries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@project7-interns project7-interns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Summary

The prior duplicate-logic blocker is resolved. weightedSpendQuery(config) now delegates to the canonical weighted_spend(...) Postgres function, and /projection uses that same RPC path while returning the required four projected-share tiers.

Findings

  • None blocking.

Decision

Approving PR #1283. The §4 worked-example coverage is present, activated/no-buy and non-eligible cases are covered, the cache header is tested, and the remaining canonical weighted-spend implementation is single-sourced through the database function. CI was still pending at review time.

@realproject7 realproject7 merged commit ccaf46b into main May 26, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants